﻿using System;
using System.Collections.Generic;
using System.Reflection;
using net.entity.framework.cache;
using net.entity.framework.common;
using net.entity.framework.db;

namespace net.entity.framework {
    public abstract class Entity {
        #region Members

        private readonly object syncLock = new object();

        private bool isDeleted;
        private readonly int id;
        private readonly int typeId;
        private readonly int revision;
        private string creator;
        private DateTime timeStamp;

        #endregion

        #region Properties

        public bool IsDeleted {
            get { return isDeleted; }
            set {
                CheckLock();
                isDeleted = value;
            }
        }

        internal int Id {
            get { return id; }
        }

        internal int TypeId {
            get { return typeId; }
        }

        internal int Revision {
            get { return revision; }
        }

        public string Creator {
            get { return creator; }
            private set { creator = value; }
        }

        public DateTime TimeStamp {
            get { return timeStamp; }
            private set { timeStamp = value; }
        }

        internal EntityStates State { get; private set; }

        internal Type ObjectType {
            get { return TypeIdCache.GetValue(typeId).EntityType; }
        }

        internal bool IsLocked { get; private set; }

        internal bool IsNew {
            get { return State == EntityStates.New; }
        }

        internal bool IsHead {
            get { return State == EntityStates.Head; }
        }

        internal bool IsModified {
            get { return State == EntityStates.Modified; }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// This is used for creating a new Entity
        /// </summary>
        protected Entity() {
            id = -1; // TODO: Get Unsafe Id
            typeId = TypeIdCache.GetTypeIdByEntityType(GetType());
            revision = 0;
            creator = string.Empty;
            timeStamp = Coding.MinDateTime;
            State = EntityStates.New;
            IsLocked = false;
        }

        protected Entity(bool isDeleted, int id, int typeId, int revision) {
            this.isDeleted = isDeleted;
            this.id = id;
            this.typeId = typeId;
            this.revision = revision;
            creator = string.Empty;
            timeStamp = Coding.MinDateTime;
            State = EntityStates.New;
            IsLocked = true;
        }

        /// <summary>
        /// To create from database
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="args"></param>
        /// <param name="creator"></param>
        /// <param name="timeStamp"></param>
        /// <returns></returns>
        internal static Entity Create(Type entityType, List<object> args, string creator, DateTime timeStamp) {
            var constructor = entityType.GetConstructorInfo(args, BindingFlags.Instance | BindingFlags.Public, true);
            var entity = (Entity) constructor.Invoke(args.ToArray());
            entity.Creator = creator;
            entity.TimeStamp = timeStamp;
            entity.State = EntityStates.Head;
            entity.IsLocked = true;
            return entity;
        }

        #endregion

        #region Methods

        public static void Commit<T>(IEnumerable<T> entities) where T : Entity {
            DbRepository.Instance.Commit(entities);
        }

        public void Commit() {
            Commit(new[] {this});
        }

        public static void Delete<T>(IEnumerable<T> entities) where T : Entity {
            DbRepository.Instance.Delete(entities);
        }

        public void Delete() {
            Delete(new[] {this});
        }

        protected T UnlockCloneForRevise<T>() where T : Entity {
            //if called accidently for a new object
            //return the same
            if (IsNew || IsModified) {
                return this as T;
            }

            //for locked objects make a clone 
            //and set the state as PendingRevise
            if (IsLocked) {
                lock (syncLock) {
                    var clone = Clone<T>();
                    clone.IsLocked = false;
                    clone.creator = string.Empty;
                    clone.timeStamp = Coding.MinDateTime;
                    clone.State = EntityStates.Modified;
                    return clone;
                }
            }
            throw new Exception(String.Format("Error while UnlockCloneForRevise for {0} Id: {1}", ObjectType.Name, Id));
        }

        /// <summary>
        /// Shallow clone
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        private T Clone<T>() where T : Entity {
            return MemberwiseClone() as T;
        }

        protected void CheckLock() {
            if (IsLocked) {
                throw new Exception(
                    "Object is locked for modification. If you intend to modify the object, use 'UnlockCloneForRevise()'");
            }
        }

        #endregion
    }

    public class EntityKey {
        #region Members

        public readonly int id;
        public readonly int typeId;
        public readonly int revision;

        #endregion

        #region Properties

        public int Id {
            get { return id; }
        }

        public int TypeId {
            get { return typeId; }
        }

        public int Revision {
            get { return revision; }
        }

        #endregion

        #region Constructore/Initializer

        public EntityKey(int id, int typeId, int revision) {
            this.id = id;
            this.typeId = typeId;
            this.revision = revision;
        }

        public static EntityKey Create(Entity entity) {
            return new EntityKey(entity.Id, entity.TypeId, entity.Revision);
        }

        public static EntityKey Create(EntityRef entityRef) {
            return new EntityKey(entityRef.Id, entityRef.TypeId, entityRef.Revision);
        }

        #endregion

        #region Methods

        public override bool Equals(object obj) {
            var other = obj as EntityKey;
            return !ReferenceEquals(other, null)
                   && id == other.id
                   && typeId == other.typeId
                   && revision == other.revision;
        }

        public override int GetHashCode() {
            return Utils.ComputeHashCode(id, typeId, revision);
        }

        public override string ToString() {
            return string.Format("Id:{0} TypeId:{1} Rev:{2}", id, typeId, revision);
        }

        #endregion
    }
}